/**
 ** This file is part of FK Klistret Extensions. FK Klistret Extensions 
 ** is free software: you can redistribute it and/or modify it under the 
 ** terms of the GNU General Public License as published by the Free 
 ** Software Foundation, either version 3 of the License, or (at your option) 
 ** any later version.

 ** FK Klistret Extensions is distributed in the hope that it will be 
 ** useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 ** General Public License for more details. You should have received a
 ** copy of the GNU General Public License along with FK Klistret Extensions. 
 ** If not, see <http://www.gnu.org/licenses/>
 */
package se.fk.klistret.migrate.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.JAXBException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import se.fk.klistret.migrate.utility.FileHelper;

import com.klistret.cmdb.ci.commons.Origin;
import com.klistret.cmdb.ci.commons.Property;
import com.klistret.cmdb.ci.element.component.Publication;
import com.klistret.cmdb.ci.element.context.PublicationType;
import com.klistret.cmdb.ci.pojo.Element;
import com.klistret.cmdb.ci.pojo.ElementType;

public class PublicationServiceImpl extends BaseElementService implements
		PublicationService {
	private static final Logger logger = LoggerFactory
			.getLogger(PublicationServiceImpl.class);

	/**
	 * Publication type
	 */
	protected ElementType publicationType;

	/**
	 * Contextual publication 
	 */
	protected ElementType contextualPublicationType;
	
	/**
	 * Contextual publication type
	 */
	protected ElementType contextualPublicationTypeType;

	/**
	 * Name pattern for archives (software similar to Ivy artifacts)
	 */
	protected static final Pattern archiveNamePattern = Pattern
			.compile("([A-Z0-9]+)_([0-9]{4}_[A-Z0-9]{3,4}+)_(([A-Z0-9]|-|_)+)(\\.|_)\\d{6}(_[A-Z0-9]{6}){0,1}\\.tar");

	/**
	 * Publication type list
	 */
	protected List<String> publicationTypes = new ArrayList<String>();

	/**
	 * Publication context list
	 */
	protected List<String> publicationContexts = new ArrayList<String>();

	/**
	 * Contextual publication
	 * 
	 * @param name
	 */
	private void doContextualPublication(String name) {
		if (!publicationContexts.contains(name)) {
			Integer count = elementService
					.count(Arrays.asList(new String[] { String
							.format("declare namespace pojo=\"http://www.klistret.com/cmdb/ci/pojo\"; /pojo:Element[pojo:name eq \"%s\" and empty(pojo:toTimeStamp)][pojo:type/pojo:name = \"{http://www.klistret.com/cmdb/ci/element/context}Publication\"]",
									name) }));

			if (count == 0) {
				Element element = new Element();
				element.setType(getContextualPublicationType());
				element.setName(name);
				element.setFromTimeStamp(new Date());
				element.setToTimeStamp(null);
				element.setCreateTimeStamp(new Date());
				element.setUpdateTimeStamp(new Date());

				com.klistret.cmdb.ci.element.context.Publication configuration = new com.klistret.cmdb.ci.element.context.Publication();
				configuration.setName(name);

				element.setConfiguration(configuration);

				elementService.create(element);
			}

			if (count > 1)
				logger.error("Publcation (context) [name: {}] exists {} times",
						name, count);
		}
		publicationContexts.add(name);
	}

	/**
	 * 
	 * @param type
	 */
	private void doContextualPublicationType(String name) {
		if (!publicationTypes.contains(name)) {
			Integer count = elementService
					.count(Arrays.asList(new String[] { String
							.format("declare namespace pojo=\"http://www.klistret.com/cmdb/ci/pojo\"; /pojo:Element[pojo:name eq \"%s\" and empty(pojo:toTimeStamp)][pojo:type/pojo:name = \"{http://www.klistret.com/cmdb/ci/element/context}PublicationType\"]",
									name) }));

			if (count == 0) {
				Element element = new Element();
				element.setType(getContextualPublicationTypeType());
				element.setName(name);
				element.setFromTimeStamp(new Date());
				element.setToTimeStamp(null);
				element.setCreateTimeStamp(new Date());
				element.setUpdateTimeStamp(new Date());

				PublicationType configuration = new PublicationType();
				configuration.setName(name);

				element.setConfiguration(configuration);

				elementService.create(element);
			}

			if (count > 1)
				logger.error("Publication type [name: {}] exists {} times",
						name, count);
		}
		publicationTypes.add(name);
	}

	@Override
	protected Element unmarshall(String line, String directory)
			throws JAXBException {
		String[] values = line.split(",");

		String originId = values[0];
		String label = FileHelper.removeQuotes(values[1]);
		Date fromTimeStamp = FileHelper.parseDate(FileHelper
				.removeQuotes(values[2]));
		Date toTimeStamp = FileHelper.parseDate(FileHelper
				.removeQuotes(values[3]));
		Date createTimeStamp = FileHelper.parseDate(FileHelper
				.removeQuotes(values[4]));
		Date updateTimeStamp = FileHelper.parseDate(FileHelper
				.removeQuotes(values[5]));

		String softwareContext = null;
		String softwareContextOriginal = null;
		String version = null;
		String type = null;

		int prefixStart = 0;

		Matcher m = archiveNamePattern.matcher(label);
		while (m.find()) {
			softwareContext = m.group(1);

			version = m.group(2);

			type = m.group(3);

			prefixStart = m.start();
		}

		String prefixes = label.substring(0, prefixStart);

		/**
		 * Special case for KND with a type equal to P or F. The special
		 * place-holder is nulled to prevent unnecessary registration of special
		 * organization type prefixing. Later it was necessary to even swap out
		 * the original software context against a concatenation against P or F.
		 * 
		 * Somehow the type (now corrected) never got reset without the trailing
		 * _P or _F suffix.
		 */
		if (softwareContext != null && softwareContext.equals("KND")
				&& type != null && (type.endsWith("_P") || type.endsWith("_F"))) {
			softwareContextOriginal = softwareContext;
			softwareContext = softwareContext.concat(type.substring(
					type.length() - 1, type.length()));

			type = type.substring(0, type.length() - 2);
			logger.debug(
					"Reformatted publication type [{}] without _P or _F suffix",
					type);
		}

		String name = (softwareContext != null && type != null) ? String
				.format("%s %s", softwareContext, type) : null;

		if (name == null)
			logger.warn("Publication (tar file) name [{}] could not be parsed",
					label);

		Element element = new Element();
		element.setType(getPublicationType());
		element.setName(name == null ? label : name);
		element.setFromTimeStamp(fromTimeStamp);
		element.setToTimeStamp(toTimeStamp);
		element.setCreateTimeStamp(createTimeStamp);
		element.setUpdateTimeStamp(updateTimeStamp);

		Publication configuration = new Publication();
		configuration.setName(element.getName());

		/**
		 * Add publication context by name if not existent
		 */
		if (name != null)
			doContextualPublication(name);

		/**
		 * Define origin
		 */
		Origin origin = new Origin();
		origin.setName("Matrix");
		origin.setIdentification(originId);
		configuration.setOrigin(origin);

		/**
		 * Organization
		 */
		configuration.setOrganization("se.fk");

		/**
		 * Set version
		 */
		if (version == null) {
			logger.warn("Version not parsed [label: {}]", label);
			configuration.getTag().add("!Version");
			configuration.setVersion("Unknown");
		} else {
			configuration.setVersion(version);
		}

		/**
		 * Prefixed software contexts which is an override of the whole
		 * sub-domain idea (only the last prefix is taken)
		 */
		for (String prefix : prefixes.split("(_|-)")) {
			prefix = prefix.trim();

			if (!prefix.equals("")) {
				Property property = new Property();

				property.setName("software.context.prefix");
				property.setValue(prefix);
				configuration.getProperty().add(property);

				logger.debug(
						"Adding software.context.prefix property {} [label: {}]",
						prefix, label);
			}
		}

		// Publication type
		if (type == null)
			configuration.setType("Unknown");
		else {
			configuration.setType(type);
			doContextualPublicationType(type);
		}

		// Publication extension
		configuration.setExtension("tar");

		// Software context property
		if (softwareContext != null) {
			Property property = new Property();
			property.setName("software.context.ancestor");
			property.setValue(softwareContext);
			configuration.getProperty().add(property);
		}

		// Original software context property
		if (softwareContextOriginal != null) {
			Property property = new Property();
			property.setName("software.context.ancestor.original");
			property.setValue(softwareContextOriginal);
			configuration.getProperty().add(property);
		}

		// Original label
		configuration.setLabel(label);

		element.setConfiguration(configuration);
		return element;
	}

	/**
	 * Get publication type
	 * 
	 * @return ElementType
	 */
	protected ElementType getPublicationType() {
		if (publicationType == null)
			publicationType = elementTypeService
					.get("{http://www.klistret.com/cmdb/ci/element/component}Publication");

		return publicationType;
	}

	/**
	 * Get contextual publication type
	 * 
	 * @return ElementType
	 */
	protected ElementType getContextualPublicationType() {
		if (contextualPublicationType == null)
			contextualPublicationType = elementTypeService
					.get("{http://www.klistret.com/cmdb/ci/element/context}Publication");

		return contextualPublicationType;
	}
	
	/**
	 * Get contextual publication type
	 * 
	 * @return ElementType
	 */
	protected ElementType getContextualPublicationTypeType() {
		if (contextualPublicationTypeType == null)
			contextualPublicationTypeType = elementTypeService
					.get("{http://www.klistret.com/cmdb/ci/element/context}PublicationType");

		return contextualPublicationTypeType;
	}
}
